package vn.vfriends.id.openid;

import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import org.openid4java.message.*;
import org.openid4java.message.ax.AxMessage;
import org.openid4java.message.ax.FetchRequest;
import org.openid4java.message.ax.FetchResponse;
import org.openid4java.message.sreg.SRegMessage;
import org.openid4java.message.sreg.SRegRequest;
import org.openid4java.message.sreg.SRegResponse;
import org.openid4java.server.InMemoryServerAssociationStore;
import org.openid4java.server.ServerManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import vn.vfriends.id.jpa.entity.User;
import vn.vfriends.id.util.Constants;
import vn.vfriends.id.util.XrdsDocumentBuilder;

/**
 *
 * @author tuan@vfriends.vn
 */
public class OpenIdProvider {

    private static final Logger log = LoggerFactory.getLogger(OpenIdProvider.class);
    private static ServerManager serverManager;

    public static ServerManager getServerManager() {
        if (serverManager == null) {
            serverManager = new ServerManager();
            serverManager.setOPEndpointUrl(getOpEndpointUrl());
            serverManager.setPrivateAssociations(new InMemoryServerAssociationStore());
            serverManager.setSharedAssociations(new InMemoryServerAssociationStore());
        }
        log.debug("Returning ServerManager =>" + serverManager.toString() + "<=");
        return serverManager;
    }

    /**
     * This is your OP endpoint. Change the URL that is returned below to match
     * your deployment scenario.
     */
    public static String getOpEndpointUrl() {
        return Constants.BASIC_URL + "/op";
    }

    /**
     *
     * @param response
     * @param request
     * @throws IOException
     */
    public static void processAssociationRequest(HttpServletResponse response, ParameterList request) throws IOException {
        Message message = getServerManager().associationResponse(request);
        sendPlainTextResponse(response, message);
    }

    /**
     * This is a helpful method to enable if you want to see what is being sent
     * across. Disable this in production.
     *
     * @param request
     */
    @SuppressWarnings("unchecked")
    public static void logRequestParameters(ParameterList request) {
        if (log.isDebugEnabled()) {
            log.debug("Dumping request parameters:");
            List<Parameter> paramList = request.getParameters();
            for (Parameter parameter : paramList) {
                log.debug(parameter.getKey() + ":" + parameter.getValue());
            }
        }
    }

    public static void sendDiscoveryResponse(HttpServletResponse response) throws IOException {
        response.setContentType("application/xrds+xml");
        OutputStream outputStream = response.getOutputStream();
        String xrdsResponse = createXrdsResponse();
        if (log.isDebugEnabled()) {
            log.debug("Sending XRDS response:");
            log.debug(xrdsResponse);
        }
        outputStream.write(xrdsResponse.getBytes());
        outputStream.close();
    }

    /**
     * TODO: document this
     *
     * @return
     */
    public static String createXrdsResponse() {
        XrdsDocumentBuilder documentBuilder = new XrdsDocumentBuilder();
        documentBuilder.addServiceElement("http://specs.openid.net/auth/2.0/server", OpenIdProvider.getOpEndpointUrl(), "10");
        documentBuilder.addServiceElement("http://specs.openid.net/auth/2.0/signon", OpenIdProvider.getOpEndpointUrl(), "20");
        documentBuilder.addServiceElement(AxMessage.OPENID_NS_AX, OpenIdProvider.getOpEndpointUrl(), "30");
        documentBuilder.addServiceElement(SRegMessage.OPENID_NS_SREG, OpenIdProvider.getOpEndpointUrl(), "40");
        return documentBuilder.toXmlString();
    }

    public static Message createAuthResponse(ParameterList requestParameters, String userSelectedId, String userSelectedClaimedId,
            boolean authenticatedAndApproved) {
        log.debug("Creating Auth Response. UserSelectedId =>" + userSelectedId + "<= userSelectedClaimedId =>" + userSelectedClaimedId + "<=");
        Message authResponse = getServerManager().authResponse(requestParameters, userSelectedId, userSelectedClaimedId, authenticatedAndApproved);
        return authResponse;
    }

    private static void sendPlainTextResponse(HttpServletResponse response, Message message) throws IOException {
        response.setContentType("text/plain");
        OutputStream os = response.getOutputStream();
        os.write(message.keyValueFormEncoding().getBytes());
        os.close();
    }

    /**
     * This is where the bulk of the action happens. Once the handshaking is
     * done and the OP is ready to send the response back to the requester, this
     * method is used to pack it all up and get it ready to ship back.
     *
     * See comments below for more details.
     *
     * @param requestParameters
     * @param userSelectedId
     * @param userSelectedClaimedId
     * @param registrationModel
     * @return
     */
    public static Message buildAuthResponse(ParameterList requestParameters,
            String userSelectedId,
            String userSelectedClaimedId,
            User loggedUser) {
        Message authResponse = OpenIdProvider.createAuthResponse(
                requestParameters,
                userSelectedId,
                userSelectedClaimedId,
                true // authenticated... yep
                );
        // Check for and process any extensions the RP has asked for
        AuthRequest authRequest = null;
        try {
            authRequest = AuthRequest.createAuthRequest(requestParameters, getServerManager().getRealmVerifier());
            // Process all of the extensions we care about...
            // Simple Registration
            if (authRequest.hasExtension(SRegMessage.OPENID_NS_SREG)) {
                log.debug("Processing Simple Registration Extension request...");
                MessageExtension extensionRequestObject = authRequest.getExtension(SRegMessage.OPENID_NS_SREG);
                if (extensionRequestObject instanceof SRegRequest) {
                    SRegRequest sRegRequest = (SRegRequest) extensionRequestObject;
                    // Send back everything we have (which you'll notice is not everything)
                    /// required or not. We're friendly...
                    Map<String, String> registrationData = new HashMap<String, String>();
                    registrationData.put("email", loggedUser.getEmail());
                    registrationData.put("fullname", loggedUser.getFullName() != null ? loggedUser.getFullName() : "");
                    SRegResponse sRegResponse = SRegResponse.createSRegResponse(sRegRequest, registrationData);
                    // Add the information to the AuthResponse message
                    authResponse.addExtension(sRegResponse);
                } else {
                    log.error("Cannot continue processing Simple Registration Extension. The object returned from the AuthRequest (of type " + extensionRequestObject.getClass().getName() + ") claims to be correct, but is not of type " + SRegRequest.class.getName() + " as expected.");
                }
            }
            if (authRequest.hasExtension(AxMessage.OPENID_NS_AX)) {
                log.debug("Processing Attribute Exchange request...");
                MessageExtension extensionRequestObject = authRequest.getExtension(AxMessage.OPENID_NS_AX);
                FetchResponse fetchResponse = null;
                Map<String, String> axData = new HashMap<String, String>();
                if (extensionRequestObject instanceof FetchRequest) {
                    FetchRequest axRequest = (FetchRequest) extensionRequestObject;
                    ParameterList parameters = axRequest.getParameters();
                    fetchResponse = FetchResponse.createFetchResponse(axRequest, axData);
                    fetchResponse.addAttribute("email", "http://schema.openid.net/contact/email", loggedUser.getEmail());
                    fetchResponse.addAttribute("fullname", "http://schema.openid.net/namePerson", loggedUser.getFullName() != null ? loggedUser.getFullName() : "");
                    authResponse.addExtension(fetchResponse);
                } else {
                    log.error("Cannot continue processing Attribute Exchange (AX) request. The object returned from the AuthRequest (of type " + extensionRequestObject.getClass().getName() + ") claims to be correct, but is not of type " + AxMessage.class.getName() + " as expected.");
                }
            }
            getServerManager().sign((AuthSuccess) authResponse);
        } catch (Exception e) {
            log.error("Error occurred creating AuthRequest object:", e);
        }
        return authResponse;
    }
}
